home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
presto
/
presto10.lha
/
src
/
process.C
< prev
next >
Wrap
C/C++ Source or Header
|
1991-12-11
|
17KB
|
678 lines
//
// process.c:
//
// Implementation of process class.
//
// Main routine p_wait is where each scheduling thread lives.
//
// Modification History:
//
// 05-Dec-91 Paul Barton-Davis
// Provided operator new() and operator delete() to replace old
// use of "this = 0" anachronism. The anachronism was already removed.
//
// 18-Jun-91 Paul Barton-Davis
// Root process ctor no longer sets affinity. This is done in
// Scheduler::invoke, after all processes have been fork(2)-ed.
// This should reduce the concurency reduction that results
// from the fact that affinity is maintained across a fork(2).
//
// 18-Jun-91 Paul Barton-Davis
// Parametized sizes of global thread freelist and local
// freelist spillover threshold. Would have been nice to have used
// something other than a global var for these, but its more work than
// is justified by the benefits.
//
// 12-Jan-90 John Faust
// Add support for processor affinity on Sequent Symmetry.
//
// 05-Dec-89 John Faust
// Remove all stack free lists. Allocate stacks of fixed size when thread
// is allocated. Stack remains associated with owning thread. More
// efficient (removes search of stack freelist for stack of proper size,
// allocation of stack if one of proper size not found, and eliminates
// need to balance stack freelists).
//
// 30-Nov-1989 John Faust
// Incorporate Bob Sandstro fix which allows gprof monitor files to be
// captured.
//
// 16-Nov-1989 John Faust
// Implement per-processor free lists of stacks.
//
// 08-Nov-1989 John Faust
// Implement per-processor free lists of thread templates.
//
#define _PROCESS_C
#include <stddef.h>
#include <sys/types.h>
#include <signal.h>
#include <osfcn.h>
#include "presto.h"
#ifdef i386
#include <sys/tmp_ctl.h>
#endif /* i386 */
Process staticproc;
Process *sysproc = &staticproc;
#ifdef GPROF
extern char *_mon_file;
static char mon_name[20];
#endif /* GPROF */
private_t Process *thisproc = 0; // always ME!
extern Main *MAIN;
//
// This is the global free list of thread templates. Deleted threads
// go here when the local free list of the owning processor is full.
// Threads are taken from here only when a local free list is exhausted.
// The local lists (see process.h) are non-shared and are thus unlocked,
// for speed. The global list is shared and is thus locked. That makes
// it slow, so we only access it when we really have to.
//
static shared_t ThreadQ thread_global_freelist (TS_ANY);
shared_t int gbl_thread_freelist_max = GBL_THREAD_FREELIST_MAX;
shared_t int lcl_thread_freelist_thresh = LCL_THREAD_FREELIST_THRESH;
//
// This constructor is called to create the root process.
//
// ptag proc constructor is useful for getting a handle on the root
// process. Since we weren't around when he got forked, we kluge
// with this.
//
Process::Process(int ptag, int id)
{
SIG_TYP p_continue; // signal handler
int i;
Thread* tlist [1+NUMPROCS+NUM_PREALLOC_THREADS];
if (ptag != P_ROOT /* || no root exists */ ) {
error("Invalid attempt to flag root Process\n");
}
//
// Create process local thread freelist, and preallocate
// some threads.
//
// The root process is responsible for creating the
// schuduler_starter thread as well as each of the main threads.
// Therefore, preallocate enough extra threads (1 for the
// scheduler_starter, one for each possible processor).
//
// Be sure to use MAINSTACKSIZE for the main threads,
// and STACKSIZE for the remaining preallocated threads.
// For now, STACKSIZE is used for all preallocated threads,
// and MAINSTACKSIZE ends up being ignored.
//
p_thread_freelist = new ThreadQUnlocked (TS_ANY);
for (i=0; i<1+NUMPROCS+NUM_PREALLOC_THREADS; i++)
tlist [i] = thisthread->newthread ("prealloc", 0, DEFSTACKSIZ);
for (i=0; i<1+NUMPROCS+NUM_PREALLOC_THREADS; i++)
free_thread (tlist[i]); // put on local freelist
p_id = id;
p_ppid = getppid();
p_pid = getpid();
p_name = "ROOT";
p_flags = P_ROOT;
p_state = S_RUN; // obviously
p_request = 0;
p_thread = 0;
p_schedthread = thisthread;
thisproc = this;
signal(SIGCONT, p_continue);
}
//
// This constructor is the one invoked by newprocess. It creates all
// processes with the exception of the root process and the original
// static process.
//
Process::Process(char* name, int id, int delayedfork)
{
int i;
Thread* tlist [NUM_PREALLOC_THREADS];
//
// Create process local thread freelist, and preallocate
// some threads.
//
p_thread_freelist = new ThreadQUnlocked (TS_ANY);
for (i=0; i<NUM_PREALLOC_THREADS; i++)
tlist [i] = thisthread->newthread ("prealloc", 0, DEFSTACKSIZ);
for (i=0; i<NUM_PREALLOC_THREADS; i++)
free_thread (tlist[i]); // put on local freelist
p_name = name;
p_request = 0;
p_flags = 0;
p_id = id;
p_schedthread = p_thread = 0;
if (!delayedfork) {
p_fork();
}
else {
p_state = S_DELAYEDFORK;
}
return;
}
//
// Constructor for staticproc (sysproc).
//
Process::Process()
{
#if (sun && THREAD_HAS_INTERRUPTIBLE_FIELD)
p_interruptible = 0;
#endif
#ifdef vax
p_interruptible = 0;
#endif /* vax */
#ifdef mips
p_interruptible = 0;
#endif
p_name = "sysproc";
p_thread_freelist = new ThreadQUnlocked (TS_ANY);
}
//
// Delay the fork until after the return of the constructor. Derived
// classes will need to take advantage of this to ensure that the
// virtual table in the derived class is properly initalized. Use
// for a derived class is:
//
// DerivedProcess::DerivedProcess(args) : (name, id, S_DELAYEDFORK)
// {
// initialize derived part
// Process::p_fork();
// /* only parent returns to here. Child never does */
// }
//
// Must be careful to use private stack when forking into child; else
// parent and child can race on the shared stack (leads to strange
// core dumps -- hard to diagnose). This should be cleaned up; use of
// asm-functions may help.
void
Process::p_fork()
{
int pid;
int spinonfork = 1; // hold child until done
static private_t int private_stack[256];
extern int _rtmp;
#ifdef sun
_rtmp = (int) &private_stack[sizeof(private_stack) / sizeof(int)];
#endif /* sun */
#ifdef sequent
_rtmp = (int) &private_stack[sizeof(private_stack) / sizeof(int)];
#endif /* sequent */
#ifdef mc68020
asm("movl sp, a0");
asm("movl __rtmp, sp");
asm("movl a0, __rtmp");
#endif /* mc68020 */
#ifdef i386
asm("xchgl %esp, __rtmp");
#endif /* i386 */
#ifdef ns32000
asm("sprd sp, r0");
asm("lprd sp, __rtmp");
asm("movd r0, __rtmp");
#endif /* ns32000 */
p_state = S_FORKING;
pid = fork();
switch (pid) {
case -1: // fork error
#ifdef mc68020
asm("movl __rtmp, sp");
#endif /* mc68020 */
#ifdef sequent
#ifdef i386
asm("movl __rtmp, %esp");
#endif /* i386 */
#ifdef ns32000
asm("lprd sp, __rtmp");
#endif /* ns32000 */
#endif /* sequent */
p_pid = -1;
return;
case 0: // child
p_ppid = getppid();
p_pid = getpid();
#ifdef GPROF
(void) sprintf (mon_name, "%s.%d", _mon_file, p_pid);
_mon_file = mon_name;
#endif /* GPROF */
p_runchild(&spinonfork);
// NOTREACHED
error("Process forked child returned???");
default: // parent: can't return until child finished with stack
#ifdef mc68020
asm("movl __rtmp, sp");
#endif /* mc68020 */
#ifdef sequent
#ifdef i386
asm("movl __rtmp, %esp");
#endif /* i386 */
#ifdef ns32000
asm("lprd sp, __rtmp");
#endif /* ns32000 */
#endif /* sequent */
p_pid = pid;
while (spinonfork)
continue;
}
}
//
// Fake being able to virtualize the constructor
//
Process*
Process::newprocess(char* name, int id)
{
return new Process(name, id);
}
// Global "wrapper function" for use in Thread::start() call.
// Allows us to access a member function correctly, instead of by
// hacking around the possibly mutable C++ calling convention.
start_Process (Process *p)
{
return p->invoke();
}
//
// Run the child scheduler. Hold onto the parent until we get all
// that we need from the args (this especially). See comments.
//
void
Process::p_runchild(int *spinonfork)
{
extern int _rtmp;
Thread* t = thisthread->newthread (p_name, 0, DEFSTACKSIZ);
#ifdef DEBUG_STARTUP
cout << "in p_runchild (after fork)\n";
#endif /* DEBUG_STARTUP */
thisthread = t; // get what we need from our parents
thisproc = this; // stack
//
_rtmp = (int)(thisthread->stack()->top());
{
#ifdef vax
// asm("movl __rtmp, sp");
#endif
#ifdef mc68020
asm("movl __rtmp, sp");
#endif /* mc68020 */
#ifdef ns32000
asm("lprd sp, __rtmp");
#endif
#ifdef i386
asm("movl __rtmp, %esp");
#endif
}
//
// should have secondary entry point into start to avoid the test
// for a scheduler.
//
//
thisthread->setflags(TF_SCHEDULER|TF_KEEPSTACK|TF_NONPREEMPTABLE);
thisthread->start(0, (PFany) start_Process, thisproc);
thisthread->setproc(thisproc);
thisproc->p_schedthread = thisthread;
*spinonfork = 0; // Let him go...
//
// The parent has just returned using the frame that our
// fp references. This means that we can never return beyond
// this routine, AND we can't reference any of the params
// passed to us on the stack (use thisthread and thisproc from
// here on).
//
//
// Set processor affinity if applicable.
//
#ifdef i386
#ifdef DEBUG_STARTUP
cout << form ("affinity = %d\n", MAIN->get_affinity()); cout.flush ();
#endif /* DEBUG_STARTUP */
if (MAIN->get_affinity ())
{
if (tmp_affinity (thisproc->id()) == -1)
cout << "unable to set affinity on process "
<< thisproc->id() << "\n";
// else
// cout << "affinity set on process "
// << getpid() << " "
// << thisproc->id() << "\n";
// cout.flush ();
}
#endif /* i386 */
#ifdef PROFILE
QProcInit(thisproc->id());
#endif
//
// Can't use run since it won't let us idle "thisthread" and run
// "thisthread" at the same time.
//
thisthread->isrunning();
thisthread->runrun(); // fall into p_wait
thisthread->isnotrunning();
//
// fall out of p_wait on its return to here
//
delete thisproc;
//
// should never get here
//
error("PROCESS DESTRUCTOR RETURNED");
}
//
// ~Process: kill a Process.
// If we are the process to be killed, then we mark ourselves as a
// zombie and _exit() (no destructors will be called). If we are
// trying to destroy another process, then we mark it
// as S_EXITING and then ask the process to actually return.
// This will have it fall back into runrun and then into
// p_runchild, where we will get called to kill it running
// as the process which is being asked to die.
//
// Doing it this way allow processes to perform whatever cleanup
// they feel like before actually disappearing.
//
// If we are the root process, we just return quietly
//
//
Process::~Process()
{
if((this->p_state&S_EXITING) == 0) {
int pid = p_pid;
if (this == thisproc) {
this->p_state = S_ZOMBIE;
if (this->isroot()) {
/* this = 0; */ // illegal anachronism
return;
}
//
// Our stack frame is gonna be all screwed up here
// since we started off with the fp running
// on our parent's frame, and he has now return.
// We can never return from this routine.
//
#ifdef GPROF
monitor(0);
#endif /* GPROF */
_exit(0);
} else {
this->p_state = S_EXITING;
this->request(R_RETURN);
}
}
}
int
Process::invoke()
{
p_wait();
return 0;
}
//
// Parent (or sibling) wakes up a looping Process here with the
// "special" request code
//
int
Process::request(int req)
{
//
// If someone is stupid enough to make a request on a proc that
// is already servicing someone else... then they are just
// gonna have to wait their turn!
//
while (p_request != R_NULL) {
if (p_request&(R_DIE|R_RETURN)) // no way to satisfy
return -1;
}
p_request = req;
if (p_state&S_OSPAUSE) { // sleeping in os
cerr << "\nWaking up " << this << "\n";
return (kill(p_pid, SIGCONT));
} else
return 0;
}
//
// p_wait: hang around and wait for something interesting
// to happen. That is, loop until someone bangs
// on our door, or until there is a readythread to
// start working on, OR we are not the root process, but our
// parent process seems to have disappeared (in which case
// we abort the scheduler, killing ourselves and all our
// siblings). We only check the last case "every so often" when
// can't find anything else to do.
// This is an example of a "heuristic." AI in action!
//
// This doesn't guarantee that the system will always stop. If
// the parent dies of as the result of an uncaught signal while
// holding a spinlock, the rest of the system could be blocked.
// If all other process are blocked, then the system will
// spin forever waiting for the holding process to relase
// the spinlock (which will never happend). Lesson:
// Don't kill -9 the root process.
//
void
Process::p_wait()
{
int idlespan = 0;
#ifdef PROFILE
QIdleLoopBegin();
#endif
for (;;) {
p_state = S_WAIT;
// wait until readythread, or until we get asynch request
if ( (p_request == NULL) && (p_thread = sched->getreadythread()) == NULL) {
if (idlespan++ == 50000) {
// check if our parent has died and we are not root
if (!isroot() && getppid() != this->ppid()) {
// kill myself and all siblings
sched->abort(-SIGKILL);
} else
idlespan = 0;
}
continue;
}
if (p_request) {
//
// assumption is that only one thread can be diddling
// with processes at a time... otherwise this might
// not work.
//
if (p_request & R_RETURN) {
// if ( (p_flags&P_ROOT) == 0)
// error("PROCS CANT RETURN YET\n");
// else
return;
}
//
// They can only die
//
if (p_request & R_DIE) {
delete this;
// not reached!
}
if (p_request & R_PARK) {
this->p_pause();
continue;
};
} else { // must have a readythread
if (p_thread->flags()&TF_SCHEDULER) {
p_thread->error("Can't schedule a scheduling thread");
continue; // NOT REACHED?
}
p_state = S_RUN;
(void)p_thread->run();
}
// others
}
}
//
// p_pause: internal version of park
//
void
Process::p_pause()
{
if (p_state != S_WAIT)
error("p_pause called to pause non spinning process");
p_state = S_OSPAUSE;
p_request = 0; // must clear here, not above
::pause();
p_state = S_WAIT;
}
//
// park: Give it a rest buddy boy
//
void
Process::park()
{
if (this == thisproc) {
cerr << "Warning: putting myself to sleep\n";
this->p_pause();
}
else {
// baby you can drive my car
p_request = R_WAKEUP|R_PARK;
}
}
void
Process::drive()
{
if (this == thisproc)
return;
else {
if (p_state != S_OSPAUSE)
cerr << "Warning: proc not parked\n";
::kill(p_pid, SIGCONT);
}
}
void
Process::error(char *s)
{
cerr << "Process error " << s << " " << this << "\n";
fatalerror();
}
// signal handler
SIG_TYP p_continue()
{
return 0;
}
//
// A process comes here when it's local thread freelist is empty.
// Get any extra threads that are lying around in the global thread freelist.
// Could optimize to lock global list and grab all threads before unlock,
// instead of doing lock-get-unlock for each thread.
//
void
Process::get_gbl_threads ()
{
register Thread* t;
int i;
if (thread_global_freelist.length() == 0) return;
for (i=0; i<lcl_thread_freelist_thresh/2-1; i++)
{
t = thread_global_freelist.get ();
if (t == 0) break;
p_thread_freelist->append (t);
}
}
//
// A process comes here when it's local thread freelist is too full.
// Put some of the extra threads into the global thread freelist.
// Could optimize to lock global list and append all extras before unlock,
// instead of doing lock-append-unlock for each thread.
//
void
Process::free_gbl_threads ()
{
Thread* t;
int i;
for (i=p_thread_freelist->length(); i > lcl_thread_freelist_thresh/2; i--)
{
t = p_thread_freelist->get ();
if (t == 0)
{
break;
}
else if (thread_global_freelist.length() > gbl_thread_freelist_max)
{
delete t;
}
else
{
thread_global_freelist.append (t);
}
}
}
void
Process::print(ostream& s)
{
s << form("(Process)=0x%x, p_id=%d, p_name=%s, p_pid=%d, p_ppid=%d, p_state=0x%x, p_request=%d",this,p_id,p_name,p_pid,p_ppid,p_state,p_request);
}